當一個系統剛上線時,使用者可能只有幾百人。
每天只有幾千筆 Request,也許一台 Server 就可以處理所有工作。
但如果有一天:
100 個使用者 -> 10,000 個使用者-> 1,000,000 個使用者
系統還撐得住嗎?
這就是今天要討論 Scalability(可擴展性),它關心的並不是:
「系統現在能不能跑?」
而是:
當業務量持續成長時,系統能不能透過增加資源,繼續維持可以接受的服務品質?
這裡有兩個很重要的觀察指標:
Throughput 可以理解成:
單位時間內,系統能完成多少工作。
例如:
Requests Per Second(RPS)
Transactions Per Second(TPS)
Messages Per Second
如果今天是一個電商系統,Throughput 可能代表:
每秒可以建立多少筆訂單
如果今天是一個支付系統,可能代表:
每秒可以處理多少筆交易
假設原本系統可以處理:
1,000 TPS
平常公司只有:
300 TPS
看起來完全沒有問題。
但活動開始後突然變成:
2,000 TPS
系統開始出現:
Queue 堆積
Timeout
Error
CPU 100%
Database Connection 耗盡
對工程師來說,這叫:
Throughput 不足。
但對公司來說可能只是:
老闆收不到訂單。
對使用者來說則是:
「為什麼一直下不了單?」
所以 Throughput 並不只是技術指標。
它其實代表:
系統能不能承接公司的業務量。
另一個重要指標是:
Latency
Latency 指的是:
從 Request 發出去,到收到 Response 所需要的時間。
例如:
GET /orders/123
如果花 100 ms,使用者通常感覺不到。變成 500 ms 大多數情況可能還可以接受。
但如果變成 5 秒,使用者就可能開始想,「是不是壞掉了?」,甚至我們自己也可以換位思考一下,你自己可以接受嗎?
所以系統不是只要做得完就可以。它還要在合理時間內做完。
假設 Monitoring 顯示:
Average Latency = 200 ms
乍看之下很正常,但實際上可能是:
90% Request = 100 ms
9% Request = 500 ms
1% Request = 10 秒
平均值仍然可能很好看。
但是那最後的 1% 使用者體驗可能非常差。
所以實務上我們還會觀察:
p50
p95
p99
例如:
p50 = 100 ms
p95 = 300 ms
p99 = 2 sec
可以理解成:
50% Request 在 100 ms 內完成
95% Request 在 300 ms 內完成
99% Request 在 2 秒內完成
因此在討論 Scalability 時,真正要觀察的不是:
「Server 有沒有掛掉?」
而是:
流量增加之後,Throughput 能不能增加,同時 Latency 還能維持在合理範圍?
假設目前系統:
10 萬會員
500 TPS
p95 = 200 ms
一年後變成:
100 萬會員
5,000 TPS
我們希望系統仍然可以保持:
p95 < 300 ms
Error Rate < 0.1%
如果做得到,就代表這個系統具有一定程度的可擴展性。
這時候最常見的兩種方法就是:
Vertical Scaling
Horizontal Scaling
也就是:
垂直擴充
水平擴充
我認為可以先簡單理解一下,一個是要求現在的員工能力更強,另外是多請一個能力差不多的員工。
垂直擴充其實很好理解。
原本:
4 Core
8 GB RAM
升級成:
8 Core
16 GB RAM
再不夠就:
16 Core
32 GB RAM
簡單來說就是:
把同一台機器變得更強。
我自己通常會認為:
在系統規模還沒有大到需要 Distributed System 時,可以優先考慮 Vertical Scaling。
原因很簡單,成本考量
不是一定每個月花的錢比較便宜,而是工程成本通常比較低。
例如原本:
Application
│
▼
Database
CPU 不夠:
4 Core
↓
8 Core
可能就解決了。
不用增加:
Load Balancer
Distributed Lock
Distributed Cache
Service Discovery
整體架構仍然相對單純。
Horizontal Scaling 則不是:
把機器變強。
而是:
增加更多機器一起處理工作。
原本:
Client
│
▼
Server
變成:
┌── Server A
Client → LB ├── Server B
└── Server C
例如:
每台 Server = 1,000 RPS
理想情況下:
1 台 = 1,000 RPS
2 台 = 2,000 RPS
4 台 = 4,000 RPS
這就是 Scale Out。
但是實際世界通常沒有這麼漂亮。
假設原本系統只有一台 Server:
Server A
現在變成:
Server A
Server B
Server C
很多原本不存在的問題就會跑出來。
例如:
如果 Session 存在 Server A:
Request 1 → Server A
Request 2 → Server B
Server B 可能完全不知道使用者登入過。
因此可能開始需要:
Redis
Distributed Cache
假設每台 Server 都會執行:
RefundJob
那:
Server A → Refund
Server B → Refund
Server C → Refund
同一筆退款可能被執行三次。
因此又可能需要:
Idempotency
Distributed Lock
Leader Election
所以 Horizontal Scaling 的真正成本通常不是:
多一台 Server 要多少錢。
而是:
當系統開始成為 Distributed System 後,會產生多少額外複雜度。
不是看到 CPU 高就一定要加 Server。
比較合理的是觀察幾個訊號。
例如:
CPU 85%
Memory 90%
而且不是瞬間 Spike,而是長時間維持高負載。
這代表單機可能已經接近 Saturation。
例如:
8 Core → 16 Core
價格增加還算合理。
但是:
16 Core → 32 Core
價格可能大幅增加。
而效能卻不會線性成長。
這時候:
2 × 16 Core
可能比:
1 × 32 Core
更有彈性。
這就是:
Vertical Scaling 的邊際效益開始下降。
即使 Performance 完全沒問題:
CPU = 30%
Memory = 40%
但是如果系統只有:
1 台 Server
那:
Server Crash
Deployment Failure
OS Update
Hardware Failure
都有可能讓整個服務中斷。
所以有時候做 Horizontal Scaling 並不是因為 Performance。
而是因為:
Availability。
例如:
Server A ❌
Server B ✅
其中一台失效,另外一台仍然可以提供服務。
假設平常:
500 RPS
活動期間:
5,000 RPS
如果全部依靠 Vertical Scaling,就代表平常也必須維持一台:
可以承受 5,000 RPS
的高規格 Server。
但是大部分時間:
90% Resource 都沒被使用。
如果是 Horizontal Scaling:
平常:
2 Instances
活動:
10 Instances
活動結束再 Scale In。
這也是 Auto Scaling 常見的使用情境。
實務上通常不是:
Scale Up
VS
Scale Out
而是:
Scale Up
+
Scale Out
例如:
2 Core
↓
4 Core
↓
8 Core
先做 Vertical Scaling。
當單機繼續升級已經不划算:
8 Core × 1
↓
8 Core × 2
↓
8 Core × 4
再逐漸走向 Horizontal Scaling。
所以比較合理的思路不是:
一開始就建立一套很複雜的 Distributed System。
而是:
隨著業務成長,逐步增加系統可以承受的 Capacity。
不一定。
假設:
1 App Server = 1,000 RPS
理論上:
2 台 = 2,000
4 台 = 4,000
但實際上可能是:
1 台 = 1,000 RPS
2 台 = 1,800 RPS
4 台 = 2,500 RPS
為什麼?
因為 Bottleneck 可能已經不在 Application Server。
而在:
Database
Redis
Message Queue
Network
3rd Party API
例如:
┌── App
├── App
Client → LB ──┼── App
├── App
└── App
│
▼
Database
App Server 可以一直增加。
但 Database 只有一台。
最後可能變成:
App CPU = 20%
Database CPU = 100%
這時候繼續增加 App Server 根本沒有意義。
所以 Scalability 最核心的一件事其實是:
找出 Bottleneck。
假設 Load Test 結果如下:
100 RPS
p95 = 100 ms
500 RPS
p95 = 120 ms
1,000 RPS
p95 = 180 ms
1,500 RPS
p95 = 500 ms
2,000 RPS
p95 = 3 sec
你會發現一件事情:
在某個時間點以前:
Traffic ↑
Throughput ↑
Latency 維持穩定
但到某個時間點之後:
Traffic ↑
Latency ↑↑↑
這代表系統開始進入 Saturation。
接下來可能出現:
Queue ↑
Timeout ↑
Retry ↑
Connection ↑
甚至最後:
Throughput ↓
所以:
最大 TPS 並不是最有意義的 Capacity 指標。
真正應該問的是:
在我們可以接受的 Latency 與 Error Rate 下,系統可以穩定處理多少 Traffic?
例如:
最大測試結果:
2,000 TPS
但是:
p95 = 5 sec
這不能真的說:
「我們系統可以扛 2,000 TPS。」
如果公司要求:
p95 < 500 ms
那真正可用的 Capacity 可能只有:
1,500 TPS
甚至更低。
很多人第一次接觸 Scalability,可能會直覺想到:
Server 不夠
→
多開幾台
但真正的 Scalability 應該是在思考:
Traffic 增加
↓
哪個 Component 先到極限?
↓
能不能透過增加資源提升 Capacity?
↓
增加資源之後 Bottleneck 移到哪裡?
例如:
App
↓
Database
↓
Cache
↓
Message Queue
↓
3rd Party
Scalability 很像是不斷在追逐 Bottleneck。
因為 Bottleneck 不會真正消失。
它只會:
移動到下一個地方。
系統真正危險的不是:
現在只有 500 TPS。
而是:
沒有人知道 1,000 TPS 時會發生什麼事情。
如果完全沒有:
Monitoring
Load Test
Capacity Planning
通常要等到 Production 出現:
CPU 100%
Connection Pool 滿
Queue 堆積
Timeout 爆炸
才開始調查。
這時候就不是:
Scalability Planning
而是:
Incident Handling。
比較健康的做法,是提前知道:
Current Traffic
Peak Traffic
Max Throughput
p95 / p99 Latency
Error Rate
CPU
Memory
DB Connection
Queue Lag
然後保留一定程度的:
Headroom
例如系統在:
2,000 TPS
就開始大幅惡化。
那我們通常不會真的讓 Production 長期跑在:
1,950 TPS
才開始處理。
而可能在:
1,200 ~ 1,500 TPS
就開始規劃下一階段的 Capacity。
因為真實世界的 Traffic 不會永遠穩定。
目前有一台 Application Server:
CPU = 20%
Memory = 30%
效能完全足夠。
但是只要這台 Server 掛掉,整個服務就會中斷。
這時候新增第二台 Server。
主要是在改善:Scalability?還是 Availability?
Load Test 結果:
1,000 TPS
p95 = 200 ms
1,500 TPS
p95 = 400 ms
2,000 TPS
p95 = 5 sec
Production Peak Traffic 已經來到:
1,300 TPS
你會等到:
2,000 TPS
才開始做 Scaling 嗎?還是應該提前開始 Capacity Planning?